#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <FS.h>
#include <Adafruit_SGP30.h>
#include <ESP8266HTTPClient.h>

#define DHTPIN  14       // D5
#define DHTTYPE       DHT22

WiFiClient wifiClient;
Adafruit_SGP30 sgp;
DHT dht(DHTPIN, DHTTYPE);

ESP8266WiFiMulti wifiMulti;     // 建立ESP8266WiFiMulti对象,对象名称是'wifiMulti'
ESP8266WebServer esp8266_server(80);// 建立网络服务器对象，该对象用于响应HTTP请求。监听端口（80）
char ssid[40];
char password[40];

String config = "/config.json";
void setup() {
  Serial.begin(9600);
  dht.begin();
  sgp.begin();

  pinMode(0, OUTPUT); //低电平触发,高电平开关 关闭
  digitalWrite(0, HIGH);
  setup_wifi();
  
  DynamicJsonDocument doc(1024);
  setup_spiffs();
  doc["ssid"] = ssid;
  doc["password"] = password;

  String output;
  serializeJson(doc, output);
  Serial.println(output);

}

void loop() {
  esp8266_server.handleClient();
}

void setup_wifi() {
  wifiMulti.addAP("666", "12345678"); // ESP8266-NodeMCU再启动后会扫描当前网络
  // 环境查找是否有这里列出的WiFi ID。如果有
  Serial.println("Connecting ...");                            // 则尝试使用此处存储的密码进行连接。

  int i = 0;
  while (wifiMulti.run() != WL_CONNECTED) {  // 此处的wifiMulti.run()是重点。通过wifiMulti.run()，NodeMCU将会在当前
    delay(1000);                             // 环境中搜索addAP函数所存储的WiFi。如果搜到多个存储的WiFi那么NodeMCU
    Serial.print(i++); Serial.print(' ');    // 将会连接信号最强的那一个WiFi信号。
  }                                          // 一旦连接WiFI成功，wifiMulti.run()将会返回“WL_CONNECTED”。这也是
  // 此处while循环判断是否跳出循环的条件。
  // WiFi连接成功后将通过串口监视器输出连接成功信息
  Serial.println('\n');                     // WiFi连接成功后
  Serial.print("Connected to ");            // NodeMCU将通过串口监视器输出。
  Serial.println(WiFi.SSID());              // 连接的WiFI名称
  Serial.print("IP address:\t");            // 以及
  Serial.println(WiFi.localIP());           // NodeMCU的IP地址

  esp8266_server.begin();                   // 启动网站服务

  esp8266_server.onNotFound(handleUserRequet);

  Serial.println("HTTP esp8266_server started");//  告知用户ESP8266网络服务功能已经启动
}


void setup_spiffs()
{
  Serial.println("mounting FS...");
  if (SPIFFS.begin())
  {
    Serial.println("mounted file system");
    if (SPIFFS.exists("/config.json"))
    {
      Serial.println("reading config file");
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile)
      {
        Serial.println("opened config file");
        size_t size = configFile.size();
        std::unique_ptr<char[]> buf(new char[size]);
        configFile.readBytes(buf.get(), size);
        DynamicJsonDocument doc(1024);
        auto error = deserializeJson(doc, buf.get());
        serializeJson(doc, Serial);
        if (!error)
        {
          Serial.println("\nparsed json");
          strcpy(ssid, doc["ssid"]);
          strcpy(password, doc["password"]);
        }
        else
        {
          Serial.println("failed to load json config");
          Serial.println(error.c_str());
        }
      }
    }
  }
  else {
    Serial.println("failed to mount FS");
  }
}

// 处理用户浏览器的HTTP访问
void handleUserRequet() {

  // 获取用户请求网址信息
  String url = esp8266_server.uri();

  if (url == "/esp8266/fanstatus") {
    char status[40];
    sprintf(status, "%d", !digitalRead(0));
    Serial.println("status:");
    Serial.println(status);
//    char data[40];
//    sprintf(data,"{\"status\": %s }", !status);
    esp8266_server.send(200, "application/json;charset=UTF-8", status);
    return;
  }

  //切换开关
  if (url == "/esp8266/switchFan") {
    char status[40];
    sprintf(status, "%d", digitalRead(0));
    esp8266_server.send(200, "text/plain", status);
    digitalWrite(0, digitalRead(0) == 1 ? LOW : HIGH);
    return;
  }

  //获取数据接口
  if (url == "/esp8266/data") {
    char data[50];
    float h = dht.readHumidity();
    float t = dht.readTemperature();

    if (isnan(h) || isnan(t)) {
      Serial.println(F("Failed to read from DHT sensor!"));
      esp8266_server.send(500, "text/plain", "Failed to read from DHT sensor!");
      return;
    }

    char temp[40];
    char humi[40];
    sprintf(temp, "%.2f", t);
    sprintf(humi, "%.2f", h);
    float temperature = 0; // [°C]
    float humidity = h; // [%RH]

    sgp.setHumidity(getAbsoluteHumidity(t, h));

    if (!sgp.IAQmeasure()) {
      Serial.println("Measurement failed");
      esp8266_server.send(500, "text/plain", "Measurement failed");
    }
    DynamicJsonDocument doc(1024);
    doc.as<JsonObject>();
    doc["tvoc"] = sgp.TVOC;
    doc["co2"] = sgp.eCO2;
    doc["temp"] = temp;
    doc["humi"] = humi;
    doc["status"] = digitalRead(0);
    String output;
    serializeJson(doc, output);
    esp8266_server.send(200, "application/json;charset=UTF-8", output);
  }

  // 通过handleFileRead函数处处理用户访问
  bool fileReadOK = handleFileRead(url);

  // 如果在SPIFFS无法找到用户访问的资源，则回复404 (Not Found)
  if (!fileReadOK) {
    esp8266_server.send(404, "text/plain", "404 Not Found");
  }
}

bool handleFileRead(String path) {            //处理浏览器HTTP访问

  if (path.endsWith("/")) {                   // 如果访问地址以"/"为结尾
    path = "/index.html";                     // 则将访问地址修改为/index.html便于SPIFFS访问
  }

  String contentType = getContentType(path);  // 获取文件类型

  if (SPIFFS.exists(path)) {                     // 如果访问的文件可以在SPIFFS中找到
    File file = SPIFFS.open(path, "r");          // 则尝试打开该文件
    esp8266_server.streamFile(file, contentType);// 并且将该文件返回给浏览器
    file.close();                                // 并且关闭文件
    return true;                                 // 返回true
  }
  return false;                                  // 如果文件未找到，则返回false
}

// MIME类型
String getContentType(String filename) {
  if (filename.endsWith(".htm")) return "text/html";
  else if (filename.endsWith(".html")) return "text/html";
  else if (filename.endsWith(".css")) return "text/css";
  else if (filename.endsWith(".js")) return "application/javascript";
  else if (filename.endsWith(".png")) return "image/png";
  else if (filename.endsWith(".gif")) return "image/gif";
  else if (filename.endsWith(".jpg")) return "image/jpeg";
  else if (filename.endsWith(".ico")) return "image/x-icon";
  else if (filename.endsWith(".xml")) return "text/xml";
  else if (filename.endsWith(".pdf")) return "application/x-pdf";
  else if (filename.endsWith(".zip")) return "application/x-zip";
  else if (filename.endsWith(".gz")) return "application/x-gzip";
  return "text/plain";
}

uint32_t getAbsoluteHumidity(float temperature, float humidity) {
  const float absoluteHumidity = 216.7f * ((humidity / 100.0f) * 6.112f * exp((17.62f * temperature) / (243.12f + temperature)) / (273.15f + temperature)); // [g/m^3]
  const uint32_t absoluteHumidityScaled = static_cast<uint32_t>(1000.0f * absoluteHumidity); // [mg/m^3]
  return absoluteHumidityScaled;
}
